package org.beanutopia.reflect;

import com.google.common.base.Nullable;
import com.google.common.base.Predicate;
import org.beanutopia.exception.PropertyDefinitionException;
import org.beanutopia.exception.PropertyManipulationException;
import org.beanutopia.function.Policy;
import org.beanutopia.slot.Slot;

import java.lang.reflect.Method;

/**
 * @author: Yardena
* @date: Oct 27, 2009 5:11:03 PM
*/
class Getter<V> implements GetMethod<V> {

  protected Policy<Object,Method> getters = new Policy<Object,Method>();
  protected String propertyName;
  protected String getterPrefix;
  protected Class<?> objectClass;
  protected Class<V> valueClass;

  Getter() {}

  Getter(@Nullable Class objectClass, String propertyName, @Nullable Class<V> valueClass, String getterPrefix) {
    this.propertyName = propertyName;
    this.getterPrefix = getterPrefix;
    this.objectClass = objectClass;
    this.valueClass = valueClass;
//    initialize();
  }

  public Getter<V> initialize() throws PropertyDefinitionException {
    if (objectClass != null) {
      try {
        findMethod(objectClass);
      } catch (Exception e) {
        throw new PropertyDefinitionException("Cannot findMethod reflector for property " + propertyName, e);
      }
    }
    return this;
  }

  public Method findMethod(Class<?> objectClass) {
    return findMethod(objectClass, valueClass);
  }

  public Policy.Rule<Object,Method> getCachedMethod(Class objectClass) {
    return getters.getRule(objectClass);
  }

  public Method findMethod(Class<?> objectClass, Class<?> valueClass) {
    Policy.Rule<Object, Method> rule = getCachedMethod(objectClass);
    Method getter;
    if (rule != null) {
      getter = rule.decision();
    } else {
      try {
        getter = objectClass.getMethod(Reflector.buildMethodName(propertyName, getterPrefix));
        if (getter != null && valueClass != null && !valueClass.isAssignableFrom(Slot.BOX.apply(getter.getReturnType()))) {
          //log("Inappropriate return type in " + getter.toGenericString() + " was expecting " + valueClass);
          getter = null;
        }
      } catch (NoSuchMethodException e) {
        getter = null;
      }
      if (getter != null) {
        getters.addRule(org.beanutopia.function.PropertyFunctions.isInstanceOf(getter.getDeclaringClass()), getter);
      } else {
        getters.addRule(org.beanutopia.function.PropertyFunctions.isSupertypeOf(objectClass), null);
      }
    }
    return getter;
  }

  @SuppressWarnings({"unchecked"})
  public V apply(Object o) {
    Method getter = findMethod(o.getClass());
    try {
      return (V) getter.invoke(o);
    } catch (Exception e) {
      throw new PropertyManipulationException("Cannot reflectively retrieve " + propertyName, e);
    }
  }

  private final Predicate<Object> predicate = new Predicate<Object>() {
    @SuppressWarnings({"unchecked"})
    public boolean apply(@Nullable Object o) {
      if (o == null) return false;
      Class c = o instanceof Class ? (Class)o : o.getClass();
      return findMethod(c) != null;
    }
  };

  public Predicate<Object> supported() { return predicate; }

} //end Getter
